package com.learner.common.exception;

import com.learner.common.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

/**
* @author ice2020x
* @Date: 2023/3/7
* @Description: 异常创建工具，用于结合全局错误代码配置创建异常实例
**/
@Slf4j
public class ExceptionUtil {

    /**
     * 错误码路径，如果有pom中有重新设置targetPath，需要单独加上targetPath/config/exceptions.properties
     */
    private static final List<String> RESOURCE_PATH_LIST = Arrays.asList("config/exceptions.properties", "META-INF/resources/config/exceptions.properties");

    /**
     * 错误码map
     */
    private static final Map<String, String> ERROR_CODE_MAP = new HashMap<>();

    /**
     * 参考Springboot自动装配逻辑，加载所有包下config/exceptions.properties的文件
     * @see SpringFactoriesLoader#loadSpringFactories(ClassLoader)
     */
    static {
        try {
            for (String resourcePath : ExceptionUtil.RESOURCE_PATH_LIST) {
                Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(resourcePath);
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String errorCode = ((String) entry.getKey()).trim();
                        String mes = properties.getProperty(errorCode);
                        if (ERROR_CODE_MAP.containsKey(errorCode)) {
                            throw new RuntimeException("存在错误码冲突，请检查并修改,冲突错误码为：" + errorCode);
                        }
                        ERROR_CODE_MAP.put(errorCode, mes);
                    }
                }
            }
        } catch (IOException ex) {
            log.error("加载错误码文件失败，失败原因为{}", ex.getMessage());
        }
    }

    /**
     * 创建异常实例
     *
     * @param exceptionClass 异常类
     * @param errorCode 错误码
     * @return 异常实例
     */
    public static BaseRuntimeException createException(Class<? extends Exception> exceptionClass, Integer errorCode) {
        return createException(exceptionClass,errorCode, null,null);
    }

    /**
     * 创建异常实例
     *
     * @param exceptionClass 异常类
     * @param errorCode 错误码
     * @param args 错误信息参数
     * @return 异常实例
     */
    public static BaseRuntimeException createException(Class<? extends Exception> exceptionClass, Integer errorCode, String... args) {

        return createException(exceptionClass,errorCode, null,args);
    }

    /**
     * 创建异常实例
     *
     * @param exceptionClass 异常类
     * @param errorCode 错误码
     * @param ex 具体异常实例
     * @return 异常实例
     */
    public static BaseRuntimeException createException(Class<? extends Exception> exceptionClass, Integer errorCode, Throwable ex) {
        if (ex instanceof BaseRuntimeException) {
            return (BaseRuntimeException) ex;
        }
        return createException(exceptionClass, errorCode, ex, null);
    }

    /**
     * 创建异常实例
     *
     * @param exceptionClass 异常类
     * @param errorCode 错误码
     * @param ex 具体异常实例
     * @param args 错误信息参数
     * @return 异常实例
     */
    public static BaseRuntimeException createException(Class<? extends Exception> exceptionClass, Integer errorCode, Throwable ex, String... args) {
        if (ex != null && ex.getClass().equals(BaseRuntimeException.class)) {
            return (BaseRuntimeException) ex;
        }

        try {
            BaseRuntimeException exception;
            if(BaseRuntimeException.class.isAssignableFrom(exceptionClass)) {
                if (ex == null) {
                    exception = (BaseRuntimeException) ConstructorUtils.invokeConstructor(exceptionClass, getMessage(errorCode, args));
                }else{
                    exception = (BaseRuntimeException) ConstructorUtils.invokeConstructor(exceptionClass, new Object[]{getMessage(errorCode, args), ex});
                }
            }else {
                exception = new BaseRuntimeException(getMessage(errorCode, args),ex);
            }


            exception.setCode(errorCode);

            return exception;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

    /**
     * 获取异常信息
     *
     * @param errorCode 错误码
     * @param args 错误信息参数
     * @return 异常信息
     */
    private static String getMessage(Integer errorCode, Object[] args) {
        String mes = ERROR_CODE_MAP.get(errorCode.toString());

        if (StringUtils.isNotEmpty(mes)) {
            Locale locale = LocaleContextHolder.getLocale();
            MessageSource messageSource = SpringContextUtils.getBean("messageSource", MessageSource.class);
            String defaultMessage = args == null ? mes : MessageFormat.format(mes, args);
            if (messageSource != null && locale != null) {
                return messageSource.getMessage(mes, args, defaultMessage, locale);
            } else {
                return defaultMessage;
            }

        }

        return errorCode.toString();
    }


}